#include "yasai.h"
#include "ui_yasai.h"

/*
 * TODO
 * Test
 * Scrollwheel paging
 * Image preloading and pre-processing
 * Keep only latest bookmark
 * Post-resize sharpen
 * ?Remove loaded bookmark if bookmarking different file
 * Frame-aware scrolling (opencv feature detection)
 * Also cache images that aren't in archives
 * ? Localisation
 * ? NSIS installer
 * ? option to scroll horizontally first
 * progressdialog polish
 * show status bar when mouseover in fullscreen
 * even more UI polish
 * (c) Julian Wong 2013
 */

Yasai::Yasai(QWidget *parent)
    :
    QMainWindow(parent),
    ui(new Ui::Yasai),
    m_arcviewWasHidden(false)
{
    ui->setupUi(this);
    // action groups
    fitGroup = new QActionGroup(this);
    fitGroup->addAction(ui->actionFitWidth);
    fitGroup->addAction(ui->actionFitHeight);
    fitGroup->addAction(ui->actionFitWindow);
    fitGroup->addAction(ui->actionDoNotFit);
    fitGroup->addAction(ui->actionFit67Visible);
    directionGroup = new QActionGroup(this);
    directionGroup->addAction(ui->actionReadRightToLeft);
    directionGroup->addAction(ui->actionReadLeftToRight);
    pageviewGroup = new QActionGroup(this);
    pageviewGroup->addAction(ui->actionPageSideBySide);
    pageviewGroup->addAction(ui->actionPageSingle);

    // trigger a refresh of the graphics view with updated view options
    QObject::connect(ui->actionFitWidth,            &QAction::triggered, this, &Yasai::refreshView);
    QObject::connect(ui->actionFitHeight,           &QAction::triggered, this, &Yasai::refreshView);
    QObject::connect(ui->actionFitWindow,           &QAction::triggered, this, &Yasai::refreshView);
    QObject::connect(ui->actionDoNotFit,            &QAction::triggered, this, &Yasai::refreshView);
    QObject::connect(ui->actionFit67Visible,        &QAction::triggered, this, &Yasai::refreshView);
    QObject::connect(ui->actionReadRightToLeft,     &QAction::triggered, this, &Yasai::refreshView);
    QObject::connect(ui->actionReadLeftToRight,     &QAction::triggered, this, &Yasai::refreshView);
    QObject::connect(ui->actionPageSideBySide,      &QAction::triggered, this, &Yasai::refreshView);
    QObject::connect(ui->actionPageSingle,          &QAction::triggered, this, &Yasai::refreshView);
    QObject::connect(ui->actionViewDetectWidePages, &QAction::triggered, this, &Yasai::refreshView);

    // splitters
    ui->centerSplitter->setStretchFactor(0, 0); // dirview not stretched
    ui->centerSplitter->setStretchFactor(1, 1); // graphicsview stretched

    // dirview
    mFileSystemModel = new QFileSystemModel(this);
    QStringList filters = QStringList() << "*.7z" << "*.zip" << "*.rar" << "*.cbr" << "*.cbz";
    filters = filters << "*.bmp" << "*.jpg" << "*.png" << "*.gif" << "*.tiff";
    mFileSystemModel->setNameFilters(filters);
    mFileSystemModel->setNameFilterDisables(false);
    mFileSystemModel->setRootPath("");
    ui->dirView->setModel(mFileSystemModel);
    ui->dirView->setSortingEnabled(true);
    ui->dirView->hideColumn(1);
    ui->dirView->hideColumn(2);
    ui->dirView->hideColumn(3);
    ui->arcView->hide();

    // graphicsview/scene
    mGraphicsScene = new QGraphicsScene(ui->graphicsView);
    // ui->graphicsView->setViewport(new QGLWidget(ui->graphicsView));
    ui->graphicsView->setScene(mGraphicsScene);
    ui->graphicsView->setBackgroundBrush(QBrush(Qt::darkGray, Qt::SolidPattern));
    ui->graphicsView->addAction(ui->menuPage->menuAction());
    ui->graphicsView->addAction(ui->menuView->menuAction());
    ui->graphicsView->addAction(ui->menuFit->menuAction());
    ui->graphicsView->addAction(ui->menuTransform->menuAction());
    ui->graphicsView->addAction(ui->menuBookmarks->menuAction());
    ui->graphicsView->addAction(ui->menuMouse->menuAction());
    QObject::connect(ui->graphicsView, &YasaiGraphicsView::openDroppedFile,    this, &Yasai::openFileAndShowInBrowser);
    QObject::connect(ui->graphicsView, &YasaiGraphicsView::activateFullScreen, this, &Yasai::on_actionFullScreen_triggered);
    QObject::connect(ui->graphicsView, &YasaiGraphicsView::changed,            this, &Yasai::refreshView);

    if (QFileInfo("portable").isFile()) mSettings = new QSettings("Yasai.ini", QSettings::IniFormat);
    else mSettings   = new QSettings(QSettings::IniFormat, QSettings::UserScope, "Yasai", "Yasai");
    console          = new DebugConsole(this);
    bookmarks        = new BookmarksDialog(this, console, QFileInfo(mSettings->fileName()).path());
    about            = new AboutDialog(this);
    mArchiveModel    = new QStandardItemModel;
    mWidePageIndexes = new QList<QPersistentModelIndex>;

    // bookmarks
    // bookmarks in menu
    QObject::connect(bookmarks, &BookmarksDialog::loadBookmark, this, &Yasai::openFileAndShowInBrowser);
    QObject::connect(ui->menuBookmarksList, SIGNAL(aboutToShow()), bookmarks, SLOT(on_MenuBookmarksListAboutToShow()));
    QObject::connect(ui->menuLastReadList, SIGNAL(aboutToShow()), bookmarks, SLOT(on_MenuLastReadListAboutToShow()));

    // progress dialog
    progressModal = new QProgressDialog(this);
    progressModal->setMaximum(0);
    progressModal->setMinimum(0);
    progressModal->setModal(true);
    progressModal->setCancelButton(0);

    // statusbar
    statusFilePath = new QLabel(this);
    statusPlace    = new QLabel(this);
    statusPage     = new QLabel(this);
    ui->statusBar->addWidget(statusFilePath);
    ui->statusBar->addPermanentWidget(statusPlace);
    ui->statusBar->addPermanentWidget(statusPage);
    int iconSize = QApplication::style()->pixelMetric(QStyle::PM_SmallIconSize);
    ui->mainToolBar->setIconSize(QSize(iconSize, iconSize));

    // stylesheets
    ui->graphicsView->setStyleSheet("QGraphicsView {border-style: none;}");
    ui->statusBar->setStyleSheet("QLabel {padding-left: 5px; padding-right: 5px;}");
    ui->mainToolBar->setStyleSheet("QToolButton {padding: 5px;}");

    mWorkerThread = new QThread(this);
    mWorker = new YasaiWorker(ui->graphicsView->verticalScrollBar()->sizeHint().width());
    mWorker->moveToThread(mWorkerThread);
    QObject::connect(mWorkerThread, &QThread::started, mWorker, &YasaiWorker::on_started);
    QObject::connect(mWorkerThread, &QThread::finished, mWorker, &YasaiWorker::on_finished);
    QObject::connect(mWorker, &YasaiWorker::workerBusy,        this, &Yasai::showModalProgressDialog);
    QObject::connect(mWorker, &YasaiWorker::workerDone,        this, &Yasai::hideModalProgressDialog);
    QObject::connect(mWorker, &YasaiWorker::showImage,         this, &Yasai::clearAndShowPixmap);
    QObject::connect(mWorker, &YasaiWorker::showTwoImages,     this, &Yasai::clearAndShowTwoPixmaps);
    QObject::connect(mWorker, &YasaiWorker::detectedWidePages, this, &Yasai::addWidePages);
    QObject::connect(mWorker, &YasaiWorker::archiveLoaded,     this, &Yasai::archiveLoaded);
    QObject::connect(this, &Yasai::openFileInWorker, mWorker, &YasaiWorker::openFile);
    QObject::connect(this, &Yasai::loadArchiveImage, mWorker, &YasaiWorker::loadArchiveImage);
    mWorkerThread->start();

    // keyboard shortcuts
    keyPageDown = new QShortcut(QKeySequence(Qt::Key_PageDown), this);
    keyPageUp   = new QShortcut(QKeySequence(Qt::Key_PageUp), this);
    keyHome     = new QShortcut(QKeySequence(Qt::Key_Home), this);
    keyEnd      = new QShortcut(QKeySequence(Qt::Key_End), this);
    keyD        = new QShortcut(QKeySequence(Qt::Key_D), this);
    keyU        = new QShortcut(QKeySequence(Qt::Key_U), this);    
    QObject::connect(keyPageDown, &QShortcut::activated, this, &Yasai::on_actionPageDownOrNext_triggered);
    QObject::connect(keyPageUp,   &QShortcut::activated, this, &Yasai::on_actionPageUpOrPrevious_triggered);
    QObject::connect(keyHome,     &QShortcut::activated, this, &Yasai::moveScrollbarsToStart);
    QObject::connect(keyEnd,      &QShortcut::activated, this, &Yasai::moveScrollbarsToEnd);
    QObject::connect(keyD,        &QShortcut::activated, this, &Yasai::on_actionPageDownOrNext_triggered);
    QObject::connect(keyU,        &QShortcut::activated, this, &Yasai::on_actionPageUpOrPrevious_triggered);

    // initialize from settings
    mSettings->beginGroup("MainWindow");
    this->move(mSettings->value("pos", this->pos()).toPoint());
    this->resize(mSettings->value("size", this->size()).toSize());
    if (mSettings->value("isMaximized", this->isMaximized()).toBool()) this->showMaximized();
    ui->mainToolBar->setVisible(mSettings->value("toolBarIsVisible", ui->mainToolBar->isVisible()).toBool());
    ui->fileBrowser->setVisible(mSettings->value("fileBrowserIsVisible", ui->fileBrowser->isVisible()).toBool());
    ui->statusBar->setVisible(mSettings->value("statusBarIsVisible", ui->statusBar->isVisible()).toBool());
    // first run
    if (!mSettings->contains("toolbarIsVisible")) ui->mainToolBar->show();
    if (!mSettings->contains("fileBrowserIsVisible")) ui->fileBrowser->show();
    if (!mSettings->contains("statusBarIsVisible")) ui->statusBar->show();
    if (!mSettings->contains("pos"))
    {
        // center the window
        this->setGeometry(QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, this->size(), qApp->desktop()->availableGeometry()));
    }
    mSettings->endGroup();
    mSettings->beginGroup("fit");
    ui->actionFitWidth->setChecked(mSettings->value("actionFitWidth", ui->actionFitWidth->isChecked()).toBool());
    ui->actionFitHeight->setChecked(mSettings->value("actionFitHeight", ui->actionFitHeight->isChecked()).toBool());
    ui->actionFitWindow->setChecked(mSettings->value("actionFitWindow", ui->actionFitWindow->isChecked()).toBool());
    ui->actionDoNotFit->setChecked(mSettings->value("actionDoNotFit", ui->actionDoNotFit->isChecked()).toBool());
    ui->actionFit67Visible->setChecked(mSettings->value("actionFit67Visible", ui->actionFit67Visible->isChecked()).toBool());
    mSettings->endGroup();
    mSettings->beginGroup("direction");
    ui->actionReadRightToLeft->setChecked(mSettings->value("actionReadRightToLeft", ui->actionReadRightToLeft->isChecked()).toBool());
    ui->actionReadLeftToRight->setChecked(mSettings->value("actionReadLeftToRight", ui->actionReadLeftToRight->isChecked()).toBool());
    mSettings->endGroup();
    mSettings->beginGroup("pageview");
    ui->actionPageSideBySide->setChecked(mSettings->value("actionPageSideBySide", ui->actionPageSideBySide->isChecked()).toBool());
    ui->actionPageSingle->setChecked(mSettings->value("actionPageSingle", ui->actionPageSingle->isChecked()).toBool());
    ui->actionViewDetectWidePages->setChecked(mSettings->value("actionViewDetectWidePages", ui->actionViewDetectWidePages->isChecked()).toBool());
    mSettings->endGroup();
    mSettings->beginGroup("bookmarks");
    ui->actionBookmarkOnClose->setChecked(mSettings->value("actionBookmarkOnClose", ui->actionBookmarkOnClose->isChecked()).toBool());
    mSettings->endGroup();
    mSettings->beginGroup("mouse");
    ui->actionMouseDragToScroll->setChecked(mSettings->value("actionMouseDragToScroll", ui->actionMouseDragToScroll->isChecked()).toBool());
    ui->actionMouseHideCursor->setChecked(mSettings->value("actionMouseHideCursor", ui->actionMouseHideCursor->isChecked()).toBool());
    mSettings->endGroup();

    // workaround for QFileSystemModel expansion behaviour
    m_scrolltoTimer = new QTimer(this);
    m_scrolltoTimer->setSingleShot(true);
    QObject::connect(m_scrolltoTimer, SIGNAL(timeout()), this, SLOT(on_scrollToTimer()));
}

Yasai::~Yasai()
{
    mWorkerThread->quit();
    mWorkerThread->wait();
    delete ui;
    delete mArchiveModel;
    delete mSettings;
    delete mWorker;
}

void Yasai::showEvent(QShowEvent *)
{
    ui->actionShowToolbar->setChecked(ui->mainToolBar->isVisible());
    ui->actionShowFileBrowser->setChecked(ui->fileBrowser->isVisible());
    ui->actionShowStatusbar->setChecked(ui->statusBar->isVisible());
    ui->dirView->sortByColumn(0, Qt::AscendingOrder);
    this->on_actionMouseDragToScroll_triggered();
}

void Yasai::closeEvent(QCloseEvent *)
{
    // if (ui->actionBookmarkOnClose->isChecked()) this->on_actionBookmarksAddPage_triggered();
    if (ui->actionBookmarkOnClose->isChecked()) this->addLastRead();

    mSettings->beginGroup("MainWindow");
    mSettings->setValue("isMaximized", this->isMaximized());
    if (!isMaximized())
    {
        mSettings->setValue("pos", this->pos());
        mSettings->setValue("size", this->size());
    }
    mSettings->setValue("toolBarIsVisible", ui->mainToolBar->isVisible());
    mSettings->setValue("fileBrowserIsVisible", ui->fileBrowser->isVisible());
    mSettings->setValue("statusBarIsVisible", ui->statusBar->isVisible());
    mSettings->endGroup();
    mSettings->beginGroup("fit");
    mSettings->setValue("actionFitWidth", ui->actionFitWidth->isChecked());
    mSettings->setValue("actionFitHeight", ui->actionFitHeight->isChecked());
    mSettings->setValue("actionFitWindow", ui->actionFitWindow->isChecked());
    mSettings->setValue("actionDoNotFit", ui->actionDoNotFit->isChecked());
    mSettings->setValue("actionFit67Visible", ui->actionFit67Visible->isChecked());
    mSettings->endGroup();
    mSettings->beginGroup("direction");
    mSettings->setValue("actionReadRightToLeft", ui->actionReadRightToLeft->isChecked());
    mSettings->setValue("actionReadLeftToRight", ui->actionReadLeftToRight->isChecked());
    mSettings->endGroup();
    mSettings->beginGroup("pageview");
    mSettings->setValue("actionPageSideBySide", ui->actionPageSideBySide->isChecked());
    mSettings->setValue("actionPageSingle", ui->actionPageSingle->isChecked());
    mSettings->setValue("actionViewDetectWidePages", ui->actionViewDetectWidePages->isChecked());
    mSettings->endGroup();
    mSettings->beginGroup("bookmarks");
    mSettings->setValue("actionBookmarkOnClose", ui->actionBookmarkOnClose->isChecked());
    mSettings->endGroup();
    mSettings->beginGroup("mouse");
    mSettings->setValue("actionMouseDragToScroll", ui->actionMouseDragToScroll->isChecked());
    mSettings->setValue("actionMouseHideCursor", ui->actionMouseHideCursor->isChecked());
    mSettings->endGroup();
}

Utils::ViewOptions Yasai::getViewOptions()
{
    Utils::ViewOptions vo;
    if (ui->actionFitDoNotEnlarge->isChecked())     vo |= Utils::ViewFitDoNotEnlarge;
    if (ui->actionFitWindow->isChecked())           vo |= Utils::ViewFitWindow;
    if (ui->actionFitHeight->isChecked())           vo |= Utils::ViewFitHeight;
    if (ui->actionFitWidth->isChecked())            vo |= Utils::ViewFitWidth;
    if (ui->actionFit67Visible->isChecked())        vo |= Utils::ViewFit67Visible;
    if (ui->actionDoNotFit->isChecked())            vo |= Utils::ViewDoNotFit;
    if (ui->actionPageSideBySide->isChecked())      vo |= Utils::ViewSideBySide;
    if (ui->actionViewDetectWidePages->isChecked()) vo |= Utils::ViewDetectWidePages;
    return vo;
}

void Yasai::clearAndShowPixmap(const QPixmap &pixmap)
{
    mGraphicsScene->clear();
    mGraphicsScene->addPixmap(pixmap);
    mGraphicsScene->setSceneRect(pixmap.rect());
    this->updateStatusBar();
}

void Yasai::clearAndShowTwoPixmaps(const QPixmap &current, const QPixmap &next)
{
    mGraphicsScene->clear();
    QPixmap left = current;
    QPixmap right = next;
    if (ui->actionReadRightToLeft->isChecked())
    {
        left = next;
        right = current;
    }
    mGraphicsScene->addPixmap(left);
    QGraphicsPixmapItem *rightItem = mGraphicsScene->addPixmap(right);
    rightItem->setX(left.width());
    mGraphicsScene->setSceneRect(mGraphicsScene->itemsBoundingRect());
    this->updateStatusBar();
}

void Yasai::addWidePages(int widePos, int nextWidePos)
{
    QModelIndexList l;
    if (widePos >= 0)
    {
        l = mArchiveModel->match(mArchiveModel->index(0,0), Qt::UserRole, widePos);
        if (!mWidePageIndexes->contains(QPersistentModelIndex(l.at(0))))
        {
            mWidePageIndexes->append(QPersistentModelIndex(l.at(0)));
        }
    }
    if (nextWidePos >= 0)
    {
        l = mArchiveModel->match(mArchiveModel->index(0,0), Qt::UserRole, nextWidePos);
        if (!mWidePageIndexes->contains(QPersistentModelIndex(l.at(0))))
        {
            mWidePageIndexes->append(QPersistentModelIndex(l.at(0)));
        }
    }
}

void Yasai::openFile(const QFileInfo &fileInfo, const QString &place)
{
    if (!fileInfo.isFile())
    {
        statusFilePath->clear();
        statusPlace->clear();
        statusPage->clear();
        return;
    }

    emit this->openFileInWorker(fileInfo, place, ui->graphicsView->rect(), this->getViewOptions(), ui->graphicsView->getZoomAdjustment());
    console->log(tr("Opening %1").arg(fileInfo.filePath()));
    statusFilePath->setText(fileInfo.filePath());
    if (Utils::isImage(fileInfo.suffix()))
    {
        mArchiveModel->clear();
        mWidePageIndexes->clear();
        ui->arcView->hide();
        return;
    }
}

void Yasai::archiveLoaded(QStandardItemModel *archiveModel, const QString &place)
{
    delete mArchiveModel;
    mWidePageIndexes->clear();
    mArchiveModel = archiveModel;
    ui->arcView->setModel(mArchiveModel);

    m_arcviewWasHidden = false;
    if (ui->arcView->isHidden())
    {
        m_arcviewWasHidden = true;
        ui->arcView->show();
    }

    if (place.isEmpty())
    {
        this->setArchiveModelIndexAndActivate(mArchiveModel->index(0,0));
        return;
    }

    QList<QStandardItem *> results = mArchiveModel->findItems(place);
    if (results.isEmpty() || results.length()>1)
    {
        console->error(tr("Invalid bookmark. No results or multiple matches found."));
        return;
    }
    QModelIndex i = mArchiveModel->indexFromItem(results.at(0));
    this->setArchiveModelIndexAndActivate(i);
}

void Yasai::on_arcView_activated(const QModelIndex &index)
{
    if (!index.isValid()) return;
    QVariant position = mArchiveModel->data(index, Qt::UserRole);
    int pos = position.toInt();
    QModelIndex nextIndex = ui->arcView->indexBelow(index);
    int nextPos = -1;
    if (nextIndex.isValid())
    {
        nextPos = mArchiveModel->data(nextIndex, Qt::UserRole).toInt();
    }
    emit this->loadArchiveImage(pos, nextPos, ui->graphicsView->rect(), this->getViewOptions(), ui->graphicsView->getZoomAdjustment());
}

void Yasai::updateStatusBar()
{
    QModelIndex index = ui->arcView->currentIndex();
    if (!index.isValid()) return;
    QString place = mArchiveModel->data(index).toString();
    QString page;
    if (mGraphicsScene->items().count() > 1)
    {
        if (ui->actionReadLeftToRight->isChecked())
        {
            place.append(" | ");
            place.append(mArchiveModel->data(ui->arcView->indexBelow(index)).toString());
        }
        else
        {
            place.prepend(" | ");
            place.prepend(mArchiveModel->data(ui->arcView->indexBelow(index)).toString());
        }
        page = QString(tr("Pages %1 and %2 of %3"))
               .arg(ui->arcView->currentIndex().row()+1)
               .arg(ui->arcView->currentIndex().row()+2)
               .arg(mArchiveModel->rowCount());
    }
    else
    {
        page = QString(tr("Page %1 of %2"))
               .arg(ui->arcView->currentIndex().row()+1)
               .arg(mArchiveModel->rowCount());
    }
    statusPlace->setText(place);
    statusPage->setText(page);
}

void Yasai::on_actionBookmarksAddPage_triggered()
{
    QFileInfo fileInfo;
    QString place;

    if (!ui->arcView->currentIndex().isValid())
    {
        QModelIndex dirIndex = ui->dirView->currentIndex();
        if (!dirIndex.isValid() || mFileSystemModel->isDir(dirIndex)) return;
        fileInfo = mFileSystemModel->fileInfo(dirIndex);
    }
    else
    {
        QString path = mArchiveModel->headerData(0, Qt::Horizontal, Qt::UserRole).toString();
        fileInfo = QFileInfo(path);
        place = mArchiveModel->data(ui->arcView->currentIndex(), Qt::DisplayRole).toString();
    }

    if (!bookmarks->saveBookmark(fileInfo, place))
    {
        console->error(tr("Failed to bookmark this page."));
        return;
    }
    statusBar()->showMessage(tr("Bookmarked %1").arg(fileInfo.fileName()), MESSAGE_TIMEOUT_MS);
}

void Yasai::addLastRead()
{
    QFileInfo fileInfo;
    QString place;

    if (!ui->arcView->currentIndex().isValid())
    {
        QModelIndex dirIndex = ui->dirView->currentIndex();
        if (!dirIndex.isValid() || mFileSystemModel->isDir(dirIndex)) return;
        fileInfo = mFileSystemModel->fileInfo(dirIndex);
    }
    else
    {
        QString path = mArchiveModel->headerData(0, Qt::Horizontal, Qt::UserRole).toString();
        fileInfo = QFileInfo(path);
        place = mArchiveModel->data(ui->arcView->currentIndex(), Qt::DisplayRole).toString();
    }

    if (!bookmarks->saveLastRead(fileInfo, place))
    {
        console->error(tr("Failed to bookmark this page."));
        return;
    }
    statusBar()->showMessage(tr("Bookmarked %1").arg(fileInfo.fileName()), MESSAGE_TIMEOUT_MS);
}

void Yasai::on_actionFullScreen_triggered()
{
    if (this->isFullScreen())
    {
        this->showNormal();
        ui->mainToolBar->setVisible(toolbarVisible);
        ui->fileBrowser->setVisible(browserVisible);
        ui->statusBar->setVisible(statusbarVisible);
        ui->actionShowToolbar->setChecked(toolbarVisible);
        ui->actionShowFileBrowser->setChecked(browserVisible);
        ui->actionShowStatusbar->setChecked(statusbarVisible);
        ui->statusBar->show();
        ui->menuBar->show();
        ui->graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
        ui->graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
    }
    else
    {
        toolbarVisible = ui->mainToolBar->isVisible();
        browserVisible = ui->fileBrowser->isVisible();
        statusbarVisible = ui->statusBar->isVisible();
        ui->mainToolBar->hide();
        ui->statusBar->hide();
        ui->menuBar->hide();
        ui->fileBrowser->hide();
        ui->actionShowToolbar->setChecked(false);
        ui->actionShowFileBrowser->setChecked(false);
        ui->actionShowStatusbar->setChecked(false);
        ui->graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
        ui->graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
        this->showFullScreen();
    }
    on_arcView_activated(ui->arcView->currentIndex());
}

void Yasai::moveScrollbarsTo(Yasai::ScrollTo scrollto)
{
    int vmax = ui->graphicsView->verticalScrollBar()->maximum();
    int vmin = ui->graphicsView->verticalScrollBar()->minimum();
    int hmax = ui->graphicsView->horizontalScrollBar()->maximum();
    int hmin = ui->graphicsView->horizontalScrollBar()->minimum();
    if (scrollto == Yasai::ScrollToStart)
    {
        ui->graphicsView->verticalScrollBar()->setValue(vmin);
        if (ui->actionReadRightToLeft->isChecked())
        {
            ui->graphicsView->horizontalScrollBar()->setValue(hmax);
            return;
        }
        ui->graphicsView->horizontalScrollBar()->setValue(hmin);
        return;
    }
    if (scrollto == Yasai::ScrollToEnd)
    {
        ui->graphicsView->verticalScrollBar()->setValue(vmax);
        if (ui->actionReadRightToLeft->isChecked())
        {
            ui->graphicsView->horizontalScrollBar()->setValue(hmin);
            return;
        }
        ui->graphicsView->horizontalScrollBar()->setValue(hmax);
        return;
    }
}

void Yasai::nextPage(ScrollTo scrollto)
{
    QModelIndex currentIndex = ui->arcView->currentIndex();
    QModelIndex nextIndex = ui->arcView->indexBelow(currentIndex);
    if (!nextIndex.isValid())
    {
        QModelIndex nextDirIndex = ui->dirView->indexBelow(ui->dirView->currentIndex());
        if (!nextDirIndex.isValid()) return;
        this->setFileSystemModelIndexAndActivate(nextDirIndex);
        return;
    }

    // if viewing single pages or wide page advance one page
    if (!ui->actionPageSideBySide->isChecked() ||
            mWidePageIndexes->contains(QPersistentModelIndex(currentIndex)) ||
            mWidePageIndexes->contains(QPersistentModelIndex(nextIndex)))
    {
        this->setArchiveModelIndexAndActivate(nextIndex);
        this->moveScrollbarsTo(scrollto);
        return;
    }
    // advance two pages. if not possible, advance one page
    QModelIndex nextNextIndex = ui->arcView->indexBelow(nextIndex);
    if (!nextNextIndex.isValid()) nextNextIndex = nextIndex;
    this->setArchiveModelIndexAndActivate(nextNextIndex);
    this->moveScrollbarsTo(scrollto);
}

void Yasai::previousPage(ScrollTo scrollto)
{
    QModelIndex currentIndex = ui->arcView->currentIndex();
    QModelIndex prevIndex = ui->arcView->indexAbove(currentIndex);

    // progress to previous item in file system if archive is not open
    if (ui->arcView->isHidden() || !prevIndex.isValid())
    {
        QModelIndex prevDirIndex = ui->dirView->indexAbove(ui->dirView->currentIndex());
        if (!prevDirIndex.isValid()) return;
        this->setFileSystemModelIndexAndActivate(prevDirIndex);
        return;
    }

    // if viewing single pages or the previous page is wide go back one page
    if (!ui->actionPageSideBySide->isChecked()
            || mWidePageIndexes->contains(QPersistentModelIndex(prevIndex)))
    {
        this->setArchiveModelIndexAndActivate(prevIndex);
        this->moveScrollbarsTo(scrollto);
        return;
    }

    // go back two pages. if that is a wide page or the behind the first page, go back only one page
    QModelIndex prevPrevIndex = ui->arcView->indexAbove(prevIndex);
    if (!prevPrevIndex.isValid()
            || mWidePageIndexes->contains(QPersistentModelIndex(prevPrevIndex)))
    {
        prevPrevIndex = prevIndex;
    }
    this->setArchiveModelIndexAndActivate(prevPrevIndex);
    this->moveScrollbarsTo(scrollto);
}


void Yasai::on_actionPageDownOrNext_triggered()
{
    int vertsv = ui->graphicsView->verticalScrollBar()->value();
    int horzsv = ui->graphicsView->horizontalScrollBar()->value();
    int vmax   = ui->graphicsView->verticalScrollBar()->maximum();
    int vmin   = ui->graphicsView->verticalScrollBar()->minimum();
    int hmax   = ui->graphicsView->horizontalScrollBar()->maximum();
    int hmin   = ui->graphicsView->horizontalScrollBar()->minimum();

    if (vertsv!=vmax)
    {
        vertsv += ui->graphicsView->verticalScrollBar()->pageStep();
        ui->graphicsView->verticalScrollBar()->setValue(vertsv);
        return;
    }
    // at the bottom of the page, but maybe not at the end
    if (ui->actionReadLeftToRight->isChecked() && horzsv!=hmax)
    {
        horzsv += ui->graphicsView->horizontalScrollBar()->pageStep();
        ui->graphicsView->horizontalScrollBar()->setValue(horzsv);
        ui->graphicsView->verticalScrollBar()->setValue(vmin);
        return;
    }
    if (ui->actionReadRightToLeft->isChecked() && horzsv!=hmin)
    {
        horzsv -= ui->graphicsView->horizontalScrollBar()->pageStep();
        ui->graphicsView->horizontalScrollBar()->setValue(horzsv);
        ui->graphicsView->verticalScrollBar()->setValue(vmin);
        return;
    }
    // at the end of the page, go to the next page
    this->nextPage(Yasai::ScrollToStart);
}

void Yasai::on_actionPageUpOrPrevious_triggered()
{
    int vertsv = ui->graphicsView->verticalScrollBar()->value();
    int horzsv = ui->graphicsView->horizontalScrollBar()->value();
    int vmax   = ui->graphicsView->verticalScrollBar()->maximum();
    int vmin   = ui->graphicsView->verticalScrollBar()->minimum();
    int hmax   = ui->graphicsView->horizontalScrollBar()->maximum();
    int hmin   = ui->graphicsView->horizontalScrollBar()->minimum();

    if (vertsv!=vmin)
    {
        vertsv -= ui->graphicsView->verticalScrollBar()->pageStep();
        ui->graphicsView->verticalScrollBar()->setValue(vertsv);
        return;
    }
    // at the top of the page, but maybe not at the start
    if (ui->actionReadLeftToRight->isChecked() && horzsv!=hmin)
    {
        horzsv -= ui->graphicsView->horizontalScrollBar()->pageStep();
        ui->graphicsView->horizontalScrollBar()->setValue(horzsv);
        ui->graphicsView->verticalScrollBar()->setValue(vmax);
        return;
    }

    if (ui->actionReadRightToLeft->isChecked() && horzsv!=hmax)
    {
        horzsv += ui->graphicsView->horizontalScrollBar()->pageStep();
        ui->graphicsView->horizontalScrollBar()->setValue(horzsv);
        ui->graphicsView->verticalScrollBar()->setValue(vmax);
        return;
    }
    // at the start of the page, go back
    this->previousPage(Yasai::ScrollToEnd);
}

void Yasai::on_actionPageNextImage_triggered()
{
    QModelIndex nextIndex = ui->arcView->indexBelow(ui->arcView->currentIndex());
    if (!nextIndex.isValid()) return;
    this->setArchiveModelIndexAndActivate(nextIndex);
    this->moveScrollbarsTo(Yasai::ScrollToStart);
}

void Yasai::on_actionPagePreviousImage_triggered()
{
    QModelIndex prevIndex = ui->arcView->indexAbove(ui->arcView->currentIndex());
    if (!prevIndex.isValid()) return;
    this->setArchiveModelIndexAndActivate(prevIndex);
    this->moveScrollbarsTo(Yasai::ScrollToStart);
}

// file, open
void Yasai::on_actionOpen_triggered()
{
    QString archiveFilter = tr("Archives (*.7z *.rar *.zip *.cbr *.cbz)");
    QString imageFilter   = tr("Images (*.bmp *.jpg *.png *.gif *.tiff)");
    QString filters       = archiveFilter + ";;" + imageFilter;
    QString filename      = QFileDialog::getOpenFileName(this, tr("Open file"), QString(), filters);
    this->openFile(QFileInfo(filename));
    ui->dirView->setCurrentIndex(mFileSystemModel->index(filename));
    m_scrolltoTimer->start(1000);
}

/* one-line or very short UI triggers below here */
void Yasai::refreshView()
{
    this->on_arcView_activated(ui->arcView->currentIndex());
}

void Yasai::setArchiveModelIndexAndActivate(const QModelIndex &index)
{
    ui->arcView->setCurrentIndex(index);
    this->on_arcView_activated(ui->arcView->currentIndex());
}

// previous page, next page
void Yasai::setFileSystemModelIndexAndActivate(const QModelIndex &index)
{
    ui->dirView->setCurrentIndex(index);    
    if (mFileSystemModel->isDir(index)) ui->dirView->expand(index);
    this->on_dirView_activated(ui->dirView->currentIndex());
}

void Yasai::on_dirView_activated(const QModelIndex &index)
{
    this->openFile(mFileSystemModel->fileInfo(index), QString());        
}

// drag and drop
// load bookmarks
void Yasai::openFileAndShowInBrowser(const QFileInfo &fileInfo, const QString &place)
{
    this->openFile(fileInfo, place);
    ui->dirView->setCurrentIndex(mFileSystemModel->index(fileInfo.filePath()));
    m_scrolltoTimer->start(1000);
}

void Yasai::showModalProgressDialog(const QString &message)
{
    progressModal->setLabelText(message);
    progressModal->show();
}

void Yasai::hideModalProgressDialog()
{
    progressModal->hide();
}

void Yasai:: on_actionExit_triggered()
{
    this->close();
}

void Yasai::on_actionShowAbout_triggered()
{
    about->show();
}

void Yasai::on_actionShowBookmarks_triggered()
{
    bookmarks->show();
}

void Yasai::on_actionShowConsole_triggered()
{
    console->show();
}

void Yasai::on_actionShowFileBrowser_triggered()
{
    ui->fileBrowser->setVisible(ui->actionShowFileBrowser->isChecked());
}

void Yasai::on_actionShowToolbar_triggered()
{
    ui->mainToolBar->setVisible(ui->actionShowToolbar->isChecked());
}

void Yasai::on_actionShowStatusbar_triggered()
{
    ui->statusBar->setVisible(ui->actionShowStatusbar->isChecked());
}

void Yasai::on_actionRotateLeft_triggered()
{
    ui->graphicsView->rotate(270);
}

void Yasai::on_actionRotateRight_triggered()
{
    ui->graphicsView->rotate(90);
}

void Yasai::on_actionFlipVertically_triggered()
{
    ui->graphicsView->scale(1, -1);
}

void Yasai::on_actionFlipHorizontally_triggered()
{
    ui->graphicsView->scale(-1, 1);
}

void Yasai::on_actionTransformReset_triggered()
{
    ui->graphicsView->setTransform(QTransform());
}

void Yasai::moveScrollbarsToStart()
{
    this->moveScrollbarsTo(Yasai::ScrollToStart);
}

void Yasai::moveScrollbarsToEnd()
{
    this->moveScrollbarsTo(Yasai::ScrollToEnd);
}

void Yasai::on_actionPageNext_triggered()
{
    this->nextPage(Yasai::ScrollToStart);
}

void Yasai::on_actionPagePrevious_triggered()
{
    this->previousPage(Yasai::ScrollToStart);
}

void Yasai::on_actionMouseDragToScroll_triggered()
{
    if (ui->actionMouseDragToScroll->isChecked())
    {
        ui->graphicsView->setDragMode(QGraphicsView::ScrollHandDrag);
    }
    else ui->graphicsView->setDragMode(QGraphicsView::NoDrag);
}

void Yasai::on_actionMouseHideCursor_triggered()
{
    ui->graphicsView->setHideCursor(ui->actionMouseHideCursor->isChecked());
}

void Yasai::centerDirview(int, int)
{
    if (m_arcviewWasHidden)
    {
        int pagestep = ui->dirView->verticalScrollBar()->pageStep();
        int val = ui->dirView->verticalScrollBar()->value();
        ui->dirView->verticalScrollBar()->setValue(val+pagestep);
    }
}

void Yasai::on_scrollToTimer()
{
    ui->dirView->scrollTo(ui->dirView->currentIndex());
}
